package com.ztesoft.zsmart.zcm.monitor.config;

import com.ztesoft.zsmart.core.log.ZSmartLogger;
import com.ztesoft.zsmart.core.spring.SpringContext;
import com.ztesoft.zsmart.zcm.core.utils.ZcmSecurityUtil;
import com.ztesoft.zsmart.zcm.monitor.mapper.nmsdb.MetricsConnectorListDtoMapper;
import com.ztesoft.zsmart.zcm.monitor.model.nmsdb.MetricsConnectorListDto;
import com.ztesoft.zsmart.zcm.monitor.model.nmsdb.MetricsConnectorListDtoExample;
import com.ztesoft.zsmart.zcm.monitor.service.metricsConnector.MetricsConnectorService;
import org.apache.commons.lang3.StringUtils;
import ru.yandex.clickhouse.BalancedClickhouseDataSource;
import ru.yandex.clickhouse.settings.ClickHouseProperties;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * ClickHouseConfig ClickHouse配置信息生成数据源
 *
 * @author zcq
 * @version 1.0
 * @since 2020/11/13
 */
public final class ClickHouseConfig {
    private static final ZSmartLogger LOGGER = ZSmartLogger.getLogger(ClickHouseConfig.class);

    public static final String CK_ADDRESS = "address";

    public static final String CK_DB = "db";

    public static final String CK_USERNAME = "username";

    public static final String CK_PASSWORD = "password";

    public static final String CK_BATCHSIZE = "batchSize";

    public static final String CK_SOCKETTIMEOUT = "socketTimeout";

    public static final String CK_CONNECTIONTIMEOUT = "connectionTimeout";

    public static final String CK_ALIVECHECKINTERVAL = "aliveCheckInterval";

    public static final String CK_INTERVAL = "interval";

    private static final List STATUS = Arrays.asList("A", "U");

    private static ClickHouseConfig instance;

    private static BalancedClickhouseDataSource dataSource;

    private ClickHouseConfig() {
    }

    public static ClickHouseConfig getInstance() {
        if (instance == null) {
            synchronized (ClickHouseConfig.class) {
                if (null != instance) {
                    return instance;
                }

                MetricsConnectorListDtoMapper metricsConnectorListDtoMapper = SpringContext
                    .getBean(MetricsConnectorListDtoMapper.class);
                MetricsConnectorListDtoExample metricsConnectorListDtoExample = new MetricsConnectorListDtoExample();
                metricsConnectorListDtoExample.createCriteria().andStateIn(STATUS).andTypeEqualTo(2);
                List<MetricsConnectorListDto> metricsConnectorListDtos = metricsConnectorListDtoMapper
                    .selectByExample(metricsConnectorListDtoExample);
                for (MetricsConnectorListDto metricsConnectorListDto : metricsConnectorListDtos) {
                    try {
                        Integer connectorId = metricsConnectorListDto.getConnectorId();
                        MetricsConnectorService metricsConnectorService = SpringContext
                            .getBean(MetricsConnectorService.class);
                        Map<String, String> clickhouseParams = metricsConnectorService.qryClickhouseParams(connectorId);
                        ClickHouseProperties properties = new ClickHouseProperties();
                        properties.setDatabase(clickhouseParams.get(CK_DB));
                        properties.setUser(clickhouseParams.get(CK_USERNAME));
                        String ckPwd = clickhouseParams.get(CK_PASSWORD);
                        properties.setPassword(StringUtils.isNotEmpty(ckPwd) ? ZcmSecurityUtil.decrypt(ckPwd) : ckPwd);
                        properties.setConnectionTimeout(Integer.parseInt(clickhouseParams.get(CK_CONNECTIONTIMEOUT)));
                        properties.setSocketTimeout(Integer.parseInt(clickhouseParams.get(CK_SOCKETTIMEOUT)));
                        // impl jdbc 包，可以用来做负载均衡；策略是随机的，但是搭建SLB之后，就可以使用nginx 的负载均衡策略了
                        BalancedClickhouseDataSource ckDataSource = new BalancedClickhouseDataSource(
                            clickhouseParams.get(CK_ADDRESS), properties);
                        // 添加集群节点存活检查
                        dataSource = ckDataSource.scheduleActualization(
                            Integer.parseInt(clickhouseParams.get(CK_ALIVECHECKINTERVAL)), TimeUnit.SECONDS);
                        instance = new ClickHouseConfig();
                        break;
                    }
                    catch (Exception e) {
                        LOGGER.error("clickhouse datasource error", e);
                    }
                }

            }
        }
        return instance;
    }

    private static BalancedClickhouseDataSource getDataSource() {
        return dataSource;
    }

    public static List<Map<String, Object>> qryResultFromClickHouse(String sql, List<Object> whereClauseValue) {
        List<Map<String, Object>> result = new ArrayList<>();
        LOGGER.info("Sql: [{}]", sql);
        BalancedClickhouseDataSource balancedClickhouseDataSource = getInstance().getDataSource();
        try (Connection connection = balancedClickhouseDataSource.getConnection();
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            PreparedStatement ps = setPreparedStatement(preparedStatement, whereClauseValue);
            ResultSet rs = ps.executeQuery()) {
            ResultSetMetaData rsmd = rs.getMetaData();
            while (rs.next()) {
                Map<String, Object> map = new HashMap<>();
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    String columnName = rsmd.getColumnName(i);
                    int columnType = rsmd.getColumnType(i);
                    if (isDivisionTypeColumn(columnType) && "NAN".equals(rs.getString(columnName).toUpperCase())) {
                        map.put(columnName, null);
                    }
                    else {
                        if (columnType == Types.TIMESTAMP) {
                            map.put(columnName,
                                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(rs.getTimestamp(columnName)));
                        }
                        else {
                            map.put(columnName, rs.getString(columnName));
                        }
                    }
                }
                result.add(map);
            }
        }
        catch (SQLException e) {
            LOGGER.error(e);
        }
        return result;
    }

    private static boolean isDivisionTypeColumn(int columnType) {
        return columnType == Types.DOUBLE || columnType == Types.FLOAT;
    }

    private static PreparedStatement setPreparedStatement(PreparedStatement preparedStatement,
        List<Object> whereClauseValue) throws SQLException {
        for (int i = 0; i < whereClauseValue.size(); i++) {
            if (whereClauseValue.get(i) instanceof Integer) {
                preparedStatement.setInt(i + 1, Integer.parseInt(whereClauseValue.get(i).toString()));
            }
            else {
                preparedStatement.setString(i + 1, whereClauseValue.get(i).toString());
            }
        }
        return preparedStatement;
    }
}
